//+------------------------------------------------------------------+
//|                                                 ExpertSignal.mqh |
//|                             Copyright 2000-2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "ExpertBase.h"  // EA基础类（提供品种、价格序列、初始化流程等通用能力）

//+------------------------------------------------------------------+
//| 宏定义：检查是否启用特定市场模型（信号模式）                     |
//| 说明：通过位掩码 `m_patterns_usage` 判断第 p 个市场模型是否启用   |
//|       （1<<p 生成对应位的掩码，与 `m_patterns_usage` 按位与运算）|
//+------------------------------------------------------------------+
#define IS_PATTERN_USAGE(p)          ((m_patterns_usage & (((int)1) << p)) != 0)

//+------------------------------------------------------------------+
//| CExpertSignal 类：EA 交易信号核心基类                             |
//| 核心定位：继承自 CExpertBase，封装交易信号的生成、过滤、参数计算逻辑， |
//|           支持多信号过滤协同（主信号+辅助过滤），提供标准化的开仓、   |
//|           平仓、反转信号接口，适配各类技术分析策略（如均线、MACD、   |
//|           K线形态等）                                             |
//| 核心逻辑：以“加权方向值”为核心，结合主信号与辅助过滤信号的投票机制，  |
//|           通过阈值判断是否生成有效信号，同时计算开仓/平仓的价格、止损、 |
//|           止盈等参数                                               |
//| 适用场景：所有需要技术分析生成交易信号的 EA 策略，尤其适合多指标融合、 |
//|           多条件筛选的复杂策略（如趋势+震荡过滤、周期共振策略）       |
//+------------------------------------------------------------------+
class CExpertSignal : public CExpertBase
  {
protected:
   //----------------------------------------------------------------
   // 核心信号参数（信号生成与参数计算依赖）
   //----------------------------------------------------------------
   double            m_base_price;     // 基准价格（用于计算开仓/平仓价格，0.0 表示使用当前市价）
   CArrayObj         m_filters;        // 辅助过滤信号数组（最多支持64个过滤信号，实现多条件协同）
   double            m_weight;         // 本信号的权重（在多信号投票中占比，默认1.0）
   int               m_patterns_usage; // 市场模型启用位掩码（标记当前信号启用的技术分析模式，默认-1=启用所有）
   int               m_general;        // 主信号索引（-1=无主信号，≥0=指定数组中某个过滤信号为主信号，负责参数计算）
   long              m_ignore;         // 过滤信号忽略位掩码（标记需忽略的辅助过滤信号，默认0=不忽略任何）
   long              m_invert;         // 过滤信号反转位掩码（标记需反转方向的辅助过滤信号，默认0=不反转任何）
   int               m_threshold_open; // 开仓信号阈值（加权方向值需≥此值才生成开仓信号，默认50）
   int               m_threshold_close;// 平仓信号阈值（加权方向值需≥此值才生成平仓信号，默认100）
   double            m_price_level;    // 挂单价格偏移量（相对于基准价格的偏移点数，默认0.0=市价单）
   double            m_stop_level;     // 止损价格偏移量（相对于开仓价格的偏移点数，默认0.0=无止损）
   double            m_take_level;     // 止盈价格偏移量（相对于开仓价格的偏移点数，默认0.0=无止盈）
   int               m_expiration;     // 挂单有效期（单位：K线根数，默认0=永久有效）
   double            m_direction;      // 加权方向值（多信号投票后的综合方向，正数=看涨，负数=看跌，EMPTY_VALUE=无效）

public:
   //----------------------------------------------------------------
   // 构造与析构函数
   //----------------------------------------------------------------
   /**
    * @brief 构造函数：初始化交易信号对象
    * @details 1. 调用父类 CExpertBase 构造函数，初始化通用能力（品种、价格序列等）；
    *          2. 设置信号核心参数默认值：
    *             - 无主信号（m_general=-1）、信号权重1.0、启用所有市场模型（m_patterns_usage=-1）；
    *             - 开仓阈值50、平仓阈值100、无挂单偏移/止损/止盈（均为0.0）；
    *             - 加权方向值初始为无效（EMPTY_VALUE），辅助过滤数组为空；
    *          3. 无需额外动态资源初始化，依赖父类完成基础配置。
    */
                     CExpertSignal(void);

   /**
    * @brief 析构函数：清理交易信号对象资源
    * @details 1. 调用父类 CExpertBase 析构函数，释放通用资源（品种、价格序列等）；
    *          2. 辅助过滤数组 m_filters 中的对象由外部管理，无需在此销毁（避免重复释放）；
    *          3. 无自定义动态资源，确保内存安全。
    */
                    ~CExpertSignal(void);

   //----------------------------------------------------------------
   // 核心参数访问接口（外部调用，设置/获取信号关键参数）
   //----------------------------------------------------------------
   /**
    * @brief 设置基准价格（用于计算开仓/平仓价格）
    * @param value [in] 基准价格（0.0 表示使用当前市价，非0值表示自定义基准价）
    */
   void              BasePrice(double value)   { m_base_price = value;      }

   /**
    * @brief 获取价格序列使用标志（重写父类方法，融合辅助过滤信号的序列需求）
    * @return int - 价格序列使用标志（组合 ENUM_USED_SERIES 枚举值，包含主信号与所有辅助过滤信号所需序列）
    * @note 跨品种/跨周期时返回0（序列由外部传入），否则整合所有过滤信号的序列需求，确保数据完整。
    */
   int               UsedSeries(void);

   /**
    * @brief 设置本信号的权重（多信号投票时的占比）
    * @param value [in] 权重值（默认1.0，可根据信号可靠性调整，如高可靠性信号设为2.0）
    */
   void              Weight(double value)      { m_weight = value;          }

   /**
    * @brief 设置市场模型启用位掩码（标记当前信号启用的技术分析模式）
    * @param value [in] 位掩码（如 0b101 表示启用第0和第2个市场模型）
    */
   void              PatternsUsage(int value)  { m_patterns_usage = value;  }

   /**
    * @brief 设置主信号索引（指定辅助过滤数组中的某个信号为主信号，负责参数计算）
    * @param value [in] 索引（-1=无主信号，由当前信号计算参数；≥0=指定数组中对应索引的过滤信号为主信号）
    */
   void              General(int value)        { m_general = value;         }

   /**
    * @brief 设置过滤信号忽略位掩码（标记需忽略的辅助过滤信号）
    * @param value [in] 位掩码（如 0b10 表示忽略第1个辅助过滤信号）
    */
   void              Ignore(long value)        { m_ignore = value;          }

   /**
    * @brief 设置过滤信号反转位掩码（标记需反转方向的辅助过滤信号）
    * @param value [in] 位掩码（如 0b01 表示反转第0个辅助过滤信号的方向，看涨变看跌）
    */
   void              Invert(long value)        { m_invert = value;          }

   /**
    * @brief 设置开仓信号阈值（加权方向值需≥此值才生成有效开仓信号）
    * @param value [in] 阈值（默认50，值越大信号越严格，减少假信号但可能错过机会）
    */
   void              ThresholdOpen(int value)  { m_threshold_open = value;  }

   /**
    * @brief 设置平仓信号阈值（加权方向值需≥此值才生成有效平仓信号）
    * @param value [in] 阈值（默认100，通常设为开仓阈值的2倍，确保平仓信号更可靠）
    */
   void              ThresholdClose(int value) { m_threshold_close = value; }

   /**
    * @brief 设置挂单价格偏移量（相对于基准价格的偏移点数）
    * @param value [in] 偏移点数（正数=挂单价格高于基准价，负数=低于基准价，0.0=市价单）
    */
   void              PriceLevel(double value)  { m_price_level = value;     }

   /**
    * @brief 设置止损价格偏移量（相对于开仓价格的偏移点数）
    * @param value [in] 偏移点数（多单：开仓价-偏移=止损价；空单：开仓价+偏移=止损价，0.0=无止损）
    */
   void              StopLevel(double value)   { m_stop_level = value;      }

   /**
    * @brief 设置止盈价格偏移量（相对于开仓价格的偏移点数）
    * @param value [in] 偏移点数（多单：开仓价+偏移=止盈价；空单：开仓价-偏移=止盈价，0.0=无止盈）
    */
   void              TakeLevel(double value)   { m_take_level = value;      }

   /**
    * @brief 设置挂单有效期（单位：K线根数）
    * @param value [in] 有效期（0=永久有效，≥1=挂单在N根K线后过期）
    */
   void              Expiration(int value)     { m_expiration = value;      }

   //----------------------------------------------------------------
   // 初始化与配置接口（完成信号对象的参数验证与资源准备）
   //----------------------------------------------------------------
   /**
    * @brief 设置魔术号（重写父类方法，同时同步到所有辅助过滤信号）
    * @param value [in] 魔术号（32位无符号整数，用于区分不同 EA 的订单）
    * @note 确保主信号与所有辅助过滤信号使用相同魔术号，避免订单管理混乱。
    */
   void              Magic(ulong value);

   /**
    * @brief 验证信号参数有效性（重写父类方法，融合辅助过滤信号的参数验证）
    * @return bool - 验证结果：true=所有参数合法，false=存在无效参数
    * @note 验证逻辑：
    *       1. 先调用父类 CExpertBase::ValidationSettings() 验证基础配置（品种、周期等）；
    *       2. 再遍历所有辅助过滤信号，调用其 ValidationSettings() 方法验证过滤参数；
    *       3. 若任一过滤信号参数无效，整体返回 false，阻止 EA 运行。
    */
   virtual bool      ValidationSettings(void);

   /**
    * @brief 初始化指标与价格序列（重写父类方法，同步初始化所有辅助过滤信号）
    * @param indicators [in] 指标集合对象指针（用于管理主信号与过滤信号的价格序列/指标）
    * @return bool - 初始化结果：true=所有信号初始化成功，false=存在初始化失败
    * @note 初始化逻辑：
    *       1. 整合主信号与所有辅助过滤信号的价格序列需求（更新 m_used_series）；
    *       2. 调用父类 CExpertBase::InitIndicators() 初始化主信号的序列/指标；
    *       3. 为所有辅助过滤信号绑定主信号的价格序列，调用其 InitIndicators() 方法；
    *       4. 确保所有信号使用统一的价格数据，避免数据不一致导致信号冲突。
    */
   virtual bool      InitIndicators(CIndicators *indicators);

   //----------------------------------------------------------------
   // 辅助过滤信号管理接口（实现多信号协同）
   //----------------------------------------------------------------
   /**
    * @brief 添加辅助过滤信号（实现多条件筛选，提升信号可靠性）
    * @param filter [in] 辅助过滤信号对象指针（需继承自 CExpertSignal）
    * @return bool - 添加结果：true=添加成功，false=添加失败（如指针无效、初始化失败）
    * @note 添加逻辑：
    *       1. 验证过滤信号指针有效性，无效则返回 false；
    *       2. 调用过滤信号的 Init() 方法，使用主信号的品种、周期、调整后点值初始化；
    *       3. 将过滤信号添加到 m_filters 数组，同步主信号的逐 Tick 标志与魔术号；
    *       4. 支持最多64个过滤信号（受位掩码 m_ignore/m_invert 长度限制）。
    */
   virtual bool      AddFilter(CExpertSignal *filter);

   //----------------------------------------------------------------
   // 开仓信号生成接口（判断是否生成多单/空单开仓信号，并计算参数）
   //----------------------------------------------------------------
   /**
    * @brief 生成多单（买入）开仓信号，并计算开仓参数
    * @param price [out] 开仓价格（市价/挂单价格，由基准价格与偏移量计算）
    * @param sl [out] 止损价格（由开仓价格与止损偏移量计算，0.0=无止损）
    * @param tp [out] 止盈价格（由开仓价格与止盈偏移量计算，0.0=无止盈）
    * @param expiration [out] 挂单有效期（由 K线根数转换为时间戳，0=永久有效）
    * @return bool - 信号结果：true=生成有效多单开仓信号，false=无信号/参数无效
    * @note 信号逻辑：
    *       1. 若加权方向值 m_direction 为 EMPTY_VALUE（无效），直接返回 false；
    *       2. 若 m_direction ≥ 开仓阈值 m_threshold_open，标记为潜在开仓信号；
    *       3. 调用 OpenLongParams() 计算开仓价格、止损、止盈、有效期等参数；
    *       4. 若参数计算失败（如价格无效），返回 false；否则返回 true。
    */
   virtual bool      CheckOpenLong(double &price, double &sl, double &tp, datetime &expiration);

   /**
    * @brief 生成空单（卖出）开仓信号，并计算开仓参数
    * @param price [out] 开仓价格（市价/挂单价格，由基准价格与偏移量计算）
    * @param sl [out] 止损价格（由开仓价格与止损偏移量计算，0.0=无止损）
    * @param tp [out] 止盈价格（由开仓价格与止盈偏移量计算，0.0=无止盈）
    * @param expiration [out] 挂单有效期（由 K线根数转换为时间戳，0=永久有效）
    * @return bool - 信号结果：true=生成有效空单开仓信号，false=无信号/参数无效
    * @note 信号逻辑与 CheckOpenLong() 对称：
    *       1. 若 -m_direction ≥ 开仓阈值 m_threshold_open，标记为潜在开仓信号；
    *       2. 其他逻辑（参数计算、有效性检查）与多单完全一致。
    */
   virtual bool      CheckOpenShort(double &price, double &sl, double &tp, datetime &expiration);

   //----------------------------------------------------------------
   // 开仓参数计算接口（根据基准价格与偏移量，计算具体开仓参数）
   //----------------------------------------------------------------
   /**
    * @brief 计算多单开仓参数（价格、止损、止盈、有效期）
    * @param price [out] 开仓价格
    * @param sl [out] 止损价格
    * @param tp [out] 止盈价格
    * @param expiration [out] 挂单有效期
    * @return bool - 计算结果：true=参数有效，false=参数无效
    * @note 计算逻辑：
    *       1. 若设置主信号（m_general≥0），调用主信号的 OpenLongParams() 计算参数；
    *       2. 若无主信号：
    *          - 基准价格：m_base_price=0.0 则使用当前卖价（m_symbol.Ask()），否则使用自定义值；
    *          - 开仓价格：基准价格 - m_price_level × 价格单位（PriceLevelUnit()），并标准化（符合品种价格精度）；
    *          - 止损价格：m_stop_level=0.0 则为0.0，否则开仓价格 - m_stop_level × 价格单位，标准化；
    *          - 止盈价格：m_take_level=0.0 则为0.0，否则开仓价格 + m_take_level × 价格单位，标准化；
    *          - 有效期：当前时间 + m_expiration × 周期秒数（PeriodSeconds(m_period)）。
    */
   virtual bool      OpenLongParams(double &price, double &sl, double &tp, datetime &expiration);

   /**
    * @brief 计算空单开仓参数（价格、止损、止盈、有效期）
    * @param price [out] 开仓价格
    * @param sl [out] 止损价格
    * @param tp [out] 止盈价格
    * @param expiration [out] 挂单有效期
    * @return bool - 计算结果：true=参数有效，false=参数无效
    * @note 计算逻辑与 OpenLongParams() 对称：
    *       1. 基准价格：m_base_price=0.0 则使用当前买价（m_symbol.Bid()）；
    *       2. 开仓价格：基准价格 + m_price_level × 价格单位；
    *       3. 止损价格：开仓价格 + m_stop_level × 价格单位；
    *       4. 止盈价格：开仓价格 - m_take_level × 价格单位；
    *       5. 有效期计算与多单一致。
    */
   virtual bool      OpenShortParams(double &price, double &sl, double &tp, datetime &expiration);

   //----------------------------------------------------------------
   // 平仓信号生成接口（判断是否生成多单/空单平仓信号，并计算价格）
   //----------------------------------------------------------------
   /**
    * @brief 生成多单（买入）平仓信号，并计算平仓价格
    * @param price [out] 平仓价格（市价/自定义价格，由基准价格决定）
    * @return bool - 信号结果：true=生成有效多单平仓信号，false=无信号/价格无效
    * @note 信号逻辑：
    *       1. 若加权方向值 m_direction 为 EMPTY_VALUE（无效），直接返回 false；
    *       2. 若 -m_direction ≥ 平仓阈值 m_threshold_close（看跌信号足够强），标记为潜在平仓信号；
    *       3. 调用 CloseLongParams() 计算平仓价格；
    *       4. 若价格计算失败（如基准价格无效），返回 false；否则返回 true。
    */
   virtual bool      CheckCloseLong(double &price);

   /**
    * @brief 生成空单（卖出）平仓信号，并计算平仓价格
    * @param price [out] 平仓价格（市价/自定义价格，由基准价格决定）
    * @return bool - 信号结果：true=生成有效空单平仓信号，false=无信号/价格无效
    * @note 信号逻辑与 CheckCloseLong() 对称：
    *       1. 若 m_direction ≥ 平仓阈值 m_threshold_close（看涨信号足够强），标记为潜在平仓信号；
    *       2. 其他逻辑（价格计算、有效性检查）与多单平仓一致。
    */
   virtual bool      CheckCloseShort(double &price);

   //----------------------------------------------------------------
   // 平仓参数计算接口（根据基准价格，计算具体平仓价格）
   //----------------------------------------------------------------
   /**
    * @brief 计算多单平仓价格
    * @param price [out] 平仓价格
    * @return bool - 计算结果：true=价格有效，false=价格无效
    * @note 计算逻辑：
    *       1. 若设置主信号（m_general≥0），调用主信号的 CloseLongParams() 计算价格；
    *       2. 若无主信号：m_base_price=0.0 则使用当前买价（m_symbol.Bid()），否则使用自定义值；
    *       3. 多单平仓使用买价（Bid），符合交易规则（买入平仓需与市场卖出价成交）。
    */
   virtual bool      CloseLongParams(double &price);

   /**
    * @brief 计算空单平仓价格
    * @param price [out] 平仓价格
    * @return bool - 计算结果：true=价格有效，false=价格无效
    * @note 计算逻辑与 CloseLongParams() 对称：
    *       1. 若无主信号：m_base_price=0.0 则使用当前卖价（m_symbol.Ask()）；
    *       2. 空单平仓使用卖价（Ask），符合交易规则（卖出平仓需与市场买入价成交）。
    */
   virtual bool      CloseShortParams(double &price);

   //----------------------------------------------------------------
   // 持仓反转信号生成接口（平仓当前持仓 + 开仓反向持仓，一体化信号）
   //----------------------------------------------------------------
   /**
    * @brief 生成多单反转信号（平仓多单 + 开仓空单）
    * @param price [out] 反转价格（平仓与开仓价格需一致，避免滑点风险）
    * @param sl [out] 新空单的止损价格
    * @param tp [out] 新空单的止盈价格
    * @param expiration [out] 新空单的挂单有效期
    * @return bool - 信号结果：true=生成有效多单反转信号，false=无信号/价格不一致
    * @note 反转逻辑：
    *       1. 先调用 CheckCloseLong() 验证是否需要平仓多单，获取平仓价格 c_price；
    *       2. 再调用 CheckOpenShort() 验证是否需要开仓空单，获取开仓价格 price；
    *       3. 若 c_price 与 price 不一致（差值超过2个点差，存在滑点风险），返回 false；
    *       4. 否则返回 true，触发“平仓多单 + 开仓空单”的反转操作。
    */
   virtual bool      CheckReverseLong(double &price, double &sl, double &tp, datetime &expiration);

   /**
    * @brief 生成空单反转信号（平仓空单 + 开仓多单）
    * @param price [out] 反转价格（平仓与开仓价格需一致，避免滑点风险）
    * @param sl [out] 新多单的止损价格
    * @param tp [out] 新多单的止盈价格
    * @param expiration [out] 新多单的挂单有效期
    * @return bool - 信号结果：true=生成有效空单反转信号，false=无信号/价格不一致
    * @note 反转逻辑与 CheckReverseLong() 对称：
    *       1. 先验证空单平仓信号（CheckCloseShort()），再验证多单开仓信号（CheckOpenLong()）；
    *       2. 确保平仓价格与开仓价格一致，避免滑点导致反转成本过高。
    */
   virtual bool      CheckReverseShort(double &price, double &sl, double &tp, datetime &expiration);

   //----------------------------------------------------------------
   // 挂单追踪调整接口（动态调整挂单价格，适配行情变化，默认未实现）
   //----------------------------------------------------------------
   /**
    * @brief 调整多单挂单价格（追踪行情，默认未实现，子类可重写）
    * @param order [in] 挂单信息对象（包含当前挂单价格、类型等）
    * @param price [out] 调整后的挂单价格
    * @return bool - 调整结果：true=调整成功，false=无需调整/未实现（默认返回 false）
    */
   virtual bool      CheckTrailingOrderLong(COrderInfo *order, double &price)  { return(false); }

   /**
    * @brief 调整空单挂单价格（追踪行情，默认未实现，子类可重写）
    * @param order [in] 挂单信息对象（包含当前挂单价格、类型等）
    * @param price [out] 调整后的挂单价格
    * @return bool - 调整结果：true=调整成功，false=无需调整/未实现（默认返回 false）
    */
   virtual bool      CheckTrailingOrderShort(COrderInfo *order, double &price) { return(false); }

   //----------------------------------------------------------------
   // 市场模型条件判断接口（子类需重写，实现具体技术分析逻辑）
   //----------------------------------------------------------------
   /**
    * @brief 判断多单（看涨）市场模型是否成立（子类重写，实现具体技术分析）
    * @return int - 模型强度（0=不成立，≥1=成立，值越大强度越高，默认返回0）
    * @note 示例：子类可重写为“均线金叉+MACD红柱”的强度判断，返回2（双条件成立）。
    */
   virtual int       LongCondition(void)                                      { return(0);     }

   /**
    * @brief 判断空单（看跌）市场模型是否成立（子类重写，实现具体技术分析）
    * @return int - 模型强度（0=不成立，≥1=成立，值越大强度越高，默认返回0）
    * @note 示例：子类可重写为“均线死叉+MACD绿柱缩短”的强度判断，返回3（多条件成立）。
    */
   virtual int       ShortCondition(void)                                     { return(0);     }

   //----------------------------------------------------------------
   // 加权方向值计算接口（多信号投票核心逻辑）
   //----------------------------------------------------------------
   /**
    * @brief 计算加权方向值（主信号与辅助过滤信号的综合方向）
    * @return double - 加权方向值（正数=看涨，负数=看跌，EMPTY_VALUE=无效）
    * @note 计算逻辑（多信号投票机制）：
    *       1. 主信号方向：m_weight × (LongCondition() - ShortCondition())（强度差值×权重）；
    *       2. 初始化投票结果 result=主信号方向，投票数量 number=主信号是否有效（result≠0则为1，否则为0）；
    *       3. 遍历所有辅助过滤信号：
    *          - 若信号被忽略（m_ignore 对应位为1），跳过；
    *          - 调用过滤信号的 Direction() 方法，获取其方向值；
    *          - 若过滤信号方向为 EMPTY_VALUE（无效），整体返回 EMPTY_VALUE；
    *          - 若信号需反转（m_invert 对应位为1），方向值取反后加入 result；否则直接加入；
    *          - 投票数量 number 加1；
    *       4. 归一化：若 number≠0，result = result / number（平均方向值，避免信号数量影响）；
    *       5. 返回归一化后的加权方向值。
    */
   virtual double    Direction(void);

   /**
    * @brief 更新加权方向值（调用 Direction() 并赋值给 m_direction）
    * @note 建议在 EA 主循环中定期调用，确保 m_direction 反映最新行情与信号状态。
    */
   void              SetDirection(void)                             { m_direction = Direction(); }
  };
//+------------------------------------------------------------------+